﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;

namespace MudEngine.Utils
{
    public class CPUGovernor
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        static extern bool GetSystemTimes(out System.Runtime.InteropServices.ComTypes.FILETIME lpIdleTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpKernelTime, out System.Runtime.InteropServices.ComTypes.FILETIME lpUserTime);

        [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        public static extern bool GetThreadTimes(IntPtr handle, out System.Runtime.InteropServices.ComTypes.FILETIME creation, out System.Runtime.InteropServices.ComTypes.FILETIME exit, out System.Runtime.InteropServices.ComTypes.FILETIME kernel, out System.Runtime.InteropServices.ComTypes.FILETIME user);
  
         [DllImport("kernel32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
         public static extern IntPtr GetCurrentThread();
  
        [StructLayout(LayoutKind.Explicit, Size = 8)]
        struct LARGE_INTEGER
        {
            [FieldOffset(0)]
            public Int64 QuadPart;
            [FieldOffset(0)]
            public UInt32 LowPart;
            [FieldOffset(4)]
            public Int32 HighPart;
        }
  
        // Last total system time and is the sum of time spent by system 
        // in kernel, user and idle mode
        private LARGE_INTEGER m_lastTotalSystemTime;
  
        // Last total time spent by this thread in kernel space and user space
        private LARGE_INTEGER m_lastThreadUsageTime;
  
        private string m_lastThreadName;
  
        // Maximum thread CPU percentage relative to system total time.
        private int m_maxCPU;
  
        public CPUGovernor(int maxCPU)
        {
            m_maxCPU = maxCPU;
        }
  
        private readonly object _lock = new object();
  
        /// <summary>
        /// 
        /// </summary>
        /// <returns></returns>
        public bool Sleep()
        {
            System.Runtime.InteropServices.ComTypes.FILETIME sysidle ;
            System.Runtime.InteropServices.ComTypes.FILETIME kerusage ;
            System.Runtime.InteropServices.ComTypes.FILETIME userusage ;
            System.Runtime.InteropServices.ComTypes.FILETIME threadkern ;
            System.Runtime.InteropServices.ComTypes.FILETIME threaduser ;
            System.Runtime.InteropServices.ComTypes.FILETIME threadcreat ;
            System.Runtime.InteropServices.ComTypes.FILETIME threadexit;
  
            LARGE_INTEGER tmpvar = new LARGE_INTEGER();
            LARGE_INTEGER thissystime = new LARGE_INTEGER();
            LARGE_INTEGER thisthreadtime = new LARGE_INTEGER();

            //Get system kernel, user and idle times
            if (!GetSystemTimes(out sysidle, out kerusage, out userusage))
                return false;

            //Get Thread user and kernel times
            if (!GetThreadTimes(GetCurrentThread(), out threadcreat, out threadexit, out threadkern, out threaduser))
                return false;

            //Calculates total system times
            //This is sum of time used by system in kernel, user and idle mode.
  
            tmpvar.LowPart = (UInt32)sysidle.dwLowDateTime;
            tmpvar.HighPart = sysidle.dwHighDateTime;
            thissystime.QuadPart = tmpvar.QuadPart;
  
            tmpvar.LowPart = (UInt32)kerusage.dwLowDateTime;
            tmpvar.HighPart = kerusage.dwHighDateTime;
            thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;
  
            tmpvar.LowPart = (UInt32)userusage.dwLowDateTime;
            tmpvar.HighPart = userusage.dwHighDateTime;
            thissystime.QuadPart = thissystime.QuadPart + tmpvar.QuadPart;

            //Calculates time spent by this thread in user and kernel mode.
  
            tmpvar.LowPart = (UInt32)threadkern.dwLowDateTime;
            tmpvar.HighPart = threadkern.dwHighDateTime;
            thisthreadtime.QuadPart = tmpvar.QuadPart;
  
            tmpvar.LowPart = (UInt32)threaduser.dwLowDateTime;
            tmpvar.HighPart = threaduser.dwHighDateTime;
            thisthreadtime.QuadPart = thisthreadtime.QuadPart + tmpvar.QuadPart;

            //Check if this is first time this function is called
            //if yes, escape rest after copying current system and thread time
            //for further use
            //Also check if the maxCPU of differences between current and previous times
            //exceeds the specified maxCPU.
            if (thisthreadtime.QuadPart != 0 && (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart) * 100) - ((thissystime.QuadPart - m_lastTotalSystemTime.QuadPart) * m_maxCPU)) > 0)
            {
                //Calculate the time interval to sleep for averaging the extra CPU usage 
                //by this thread.
  
                LARGE_INTEGER timetosleepin100ns = new LARGE_INTEGER();
                timetosleepin100ns.QuadPart = (((thisthreadtime.QuadPart - m_lastThreadUsageTime.QuadPart) * 100) / m_maxCPU) - (thissystime.QuadPart - m_lastTotalSystemTime.QuadPart);
  
                //check if time is less than a millisecond, if yes, keep it for next time.
                if ((timetosleepin100ns.QuadPart / 10000) <= 0)
                return false;
  
                //Time to Sleep 
                Thread.Sleep((int)timetosleepin100ns.QuadPart / 10000);
            }
  
            //Copy usage time values for next time calculations.
            m_lastTotalSystemTime.QuadPart = thissystime.QuadPart;
            m_lastThreadUsageTime.QuadPart = thisthreadtime.QuadPart;
            m_lastThreadName = Thread.CurrentThread.Name;
  
            return true;
        }
    }
}
